﻿using Esst.Dto.Request.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using Esst.Dto.Response;
using Esst.Dto.Response.Auth;
using Esst.Dto.WebApp;
using Esst.Common;
using Esst.Dto.Model.Auth;
using Esst.Common.Extensions;
using Esst.IService;
using Esst.IService.Auth;
using Esst.Dto.Config;

namespace Esst.WebApi.Controllers.Auth
{
    public class AuthorizationController : Controller
    {
        private readonly IRedisRepository _redisRepository;
        private readonly ISysUserService _userService;
        private readonly IOnlineUserService _onlineUserService;

        public AuthorizationController(IRedisRepository redisRepository, ISysUserService sysUserService, IOnlineUserService onlineUserService)
        {
            _redisRepository = redisRepository;
            _userService = sysUserService;
            _onlineUserService = onlineUserService;
        }

        [HttpPost]
        [Route("login")]
        [Description("Action.UserLogin")]
        [AllowAnonymous]
        public async Task<ResponseHolder<LoginResultVo>> Login([FromBody] LoginAuthUser authUser)
        {
            ResponseHolder<LoginResultVo> response = new ResponseHolder<LoginResultVo>();
            var loginFailedLimitOptions = AppSettings.Get<LoginFailedLimitOptions>("LoginFailedLimit");
            var attempsCacheKey = $"{typeof(LoginFailedLimitOptions).FullName}-{authUser.Username}";
            LoginAttemptModel loginAttempt = new LoginAttemptModel();
            if (loginFailedLimitOptions.Enabled)
            {
                loginAttempt = await _redisRepository.Get<LoginAttemptModel>(attempsCacheKey);
                if (loginAttempt.IsNull())
                {
                    //初始化登录失败次数
                    loginAttempt = new LoginAttemptModel
                    {
                        Count = 0,
                        IsLocked = false,
                        LockUntil = DateTime.MinValue
                    };
                    await _redisRepository.Set(attempsCacheKey, loginAttempt, TimeSpan.FromSeconds(loginFailedLimitOptions.Lockout));
                }
                if (loginAttempt.IsLocked && DateTime.Now < loginAttempt.LockUntil)
                {
                    // 可以实施账户锁定时，通过邮件或短信通知用户。
                    // 可以实施账户锁定后要求管理员手动解锁

                    return response = new ResponseHolder<LoginResultVo> { State = ResponseState.Error, Message = string.Format("账号已被锁定，请在{0}后解锁", loginAttempt.LockUntil.ToString("yyyy-MM-dd HH:mm:ss")) };
                }
            }
            var captchaOptions = AppSettings.Get<CaptchaOptions>("Captcha");
            var showCaptcha = true; //是否显示验证码
            var thresholdCacheKey = $"{typeof(CaptchaOptions).FullName}-{authUser.Username}";
            var failedThreshold = 0;
            if (captchaOptions.Threshold > 0)
            {
                failedThreshold = await _redisRepository.Get<int>(thresholdCacheKey);
                if (failedThreshold <= 0)
                {
                    failedThreshold = 1;
                    await _redisRepository.Set(thresholdCacheKey, failedThreshold,
                        TimeSpan.FromSeconds(captchaOptions.TimeOut));
                }

                showCaptcha = failedThreshold > captchaOptions.Threshold;
            }
            var user = _userService.Query(a => a.LoginAccount.Equals(authUser.Username)).GetAwaiter().GetResult().FirstOrDefault();
            if (user == null)
            {
                if (captchaOptions.Threshold > 0)
                {
                    failedThreshold++;
                    await _redisRepository.Set(thresholdCacheKey, failedThreshold, TimeSpan.FromSeconds(captchaOptions.TimeOut));
                }
                return response = new ResponseHolder<LoginResultVo> { State = ResponseState.Error, Message = string.Format("账号或密码错误") };
            };

            if (user.Password.Equals(authUser.Password))
            {
                if (captchaOptions.Threshold > 0)
                {
                    failedThreshold++;

                    await _redisRepository.Set(thresholdCacheKey, failedThreshold, TimeSpan.FromSeconds(captchaOptions.TimeOut));
                }

                if (loginFailedLimitOptions.Enabled && loginAttempt != null)
                {
                    loginAttempt.Count++;
                    if (loginAttempt.Count >= loginFailedLimitOptions.MaxAttempts)
                    {
                        loginAttempt.IsLocked = true;
                        loginAttempt.LockUntil = DateTime.Now.AddSeconds(loginFailedLimitOptions.Lockout);
                    }

                    await _redisRepository.Set(attempsCacheKey, loginAttempt, TimeSpan.FromSeconds(loginFailedLimitOptions.Lockout));
                }
                return response = new ResponseHolder<LoginResultVo> { State = ResponseState.Error, Message = string.Format("账号或密码错误") };
            }

            if (!user.IsEnabled)
            {
                if (captchaOptions.Threshold > 0)
                {
                    failedThreshold++;
                    await _redisRepository.Set(thresholdCacheKey, failedThreshold,
                        TimeSpan.FromSeconds(captchaOptions.TimeOut));
                }

                return response = new ResponseHolder<LoginResultVo> { State = ResponseState.Error, Message = string.Format("账号不可用") };
            }
            await _redisRepository.Remove(authUser.CaptchaId);
            await _redisRepository.Remove(thresholdCacheKey);
            await _redisRepository.Remove(attempsCacheKey);
            return response;
        }

        #region 私有方法

        /// <summary>
        /// 登录或刷新token相应结果
        /// </summary>
        /// <param name="userDto"></param>
        /// <param name="type">login:登录,refresh:刷新token</param>
        /// <returns></returns>
        private async Task<ResponseHolder<LoginResultVo>> LoginResult(UserVo userDto, string type)
        {
            var permissionIdentifierList = new List<string>();
            var refresh = true;
            if (type.Equals("login"))
            {
                refresh = false;
                //permissionIdentifierList = await _permissionService.GetPermissionIdentifierAsync(userDto.Id);
                //permissionIdentifierList.AddRange(userDto.Roles.Select(r => r.Permission));
            }

            var remoteIp = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0";

            var jwtUserVo = await _onlineUserService.CreateJwtUserAsync(userDto, permissionIdentifierList);

            var loginUserInfo = await _onlineUserService.SaveLoginUserAsync(jwtUserVo, remoteIp);
            var token = await _tokenService.IssueTokenAsync(loginUserInfo, refresh);
            loginUserInfo.AccessToken = refresh ? token.RefreshToken : token.AccessToken;
            var onlineKey = loginUserInfo.AccessToken.ToMd5String16();
            await _redisRepository.Set(
                "online" + onlineKey,
                loginUserInfo, TimeSpan.FromHours(2));

            switch (type)
            {
                case "login":
                    var response = new LoginResultVo
                    {
                        JwtUserVo = jwtUserVo,
                        TokenVo = token
                    };
                    return JsonContent(response);

                case "refresh":
                    return JsonContent(token);

                default:
                    return Ok(OperateResult.Error("Parameter error."));
            }
        }

        #endregion 私有方法
    }
}